﻿/*!
 * Copyright (c), 上海沃恩信息科技有限公司.
 * All rights reserved.
 * Licensed under BSD (https://www.pittypat.work/bsd.html).
 */

using System;
using System.Net;
using System.Net.Http;
using System.Net.WebSockets;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Web;

namespace Pittypat.Web
{
    /// <summary>
    /// 在 HttpServer 之后根据路由将请求调度给目标模块。
    /// </summary>
    sealed class ApiHandler : DelegatingHandler
    {
        private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(ApiHandler));

        /// <summary>
        /// 初始化 <see cref="ApiHandler"/> 类的新实例。
        /// </summary>
        internal ApiHandler()
        {
        }

        /// <summary>
        /// 异步处理指定的请求。
        /// </summary>
        /// <param name="request">目标请求。</param>
        /// <param name="cancellationToken">取消异步操作的标记。</param>
        /// <returns>发送到客户端的响应消息。</returns>
        protected override async Task<HttpResponseMessage> SendAsync(
            HttpRequestMessage request, CancellationToken cancellationToken)
        {
            try
            {
                if (HttpContext.Current != null && HttpContext.Current.IsWebSocketRequest)
                {
                    // 处理 WebSocket 请求
                    return await ProcessSocketAsync(request, cancellationToken);
                }
                else
                {
                    // 处理 AJAX 请求
                    return await ProcessAjaxAsync(request, cancellationToken);
                }
            }
            catch (Exception exc)
            {
                // 原始异常错误信息
                var error = exc is TargetInvocationException && exc.InnerException != null ? exc.InnerException : exc;

                // 处理请求过程中发生错误
                Logger.Fatal(exc.Message, error);

                // 向客户端响应错误信息
                return this.CreateResponse(request, Message.Create(error));
            }
        }

        /// <summary>
        /// 异步处理 AJAX 请求。
        /// </summary>
        /// <param name="request">目标请求。</param>
        /// <param name="cancellationToken">取消异步操作的标记。</param>
        /// <returns>发送到客户端的响应消息。</returns>
        async Task<HttpResponseMessage> ProcessAjaxAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            // AJAX 请求，此时强制使用 POST 方法
            if (request.Method != HttpMethod.Post)
            {
                // 如果没有使用 POST，返回一个 405（Method Not Allowed）
                return request.CreateResponse(HttpStatusCode.MethodNotAllowed);
            }
            else
            {
                Message message = null;

                // 请求需要的异步进度
                using (var progress = new WebAsyncProgress(cancellationToken))
                {
                    // 初始化和处理请求，返回请求处理的结果消息
                    message = await (await Request.CreateAsync(request, progress)).ProcessAjaxAsync();
                }

                // 将消息发送到客户端
                return this.CreateResponse(request, message);
            }
        }
        
        /// <summary>
        /// 异步处理 WebSocket 请求。
        /// </summary>
        /// <param name="request">目标请求。</param>
        /// <param name="cancellationToken">取消异步操作的标记。</param>
        /// <returns>发送到客户端的响应消息。</returns>
        Task<HttpResponseMessage> ProcessSocketAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            // WebSocket 请求子协议
            var subprotocol = request.GetWebSocketSubProtocol();
            
            // 接受 WebSocket 请求
            HttpContext.Current.AcceptWebSocketRequest(async socketContext =>
            {
                // WebSocket 连接
                var socket = socketContext.WebSocket;

                if (socket.State == WebSocketState.Open)
                {
                    // 如果连接已经打开，初始化异步操作的进度
                    using (var progress = new WebSocketAsyncProgress(socket, cancellationToken))
                    {
                        try
                        {
                            // 初始化和处理请求
                            await (await Request.CreateAsync(request, progress, socket, subprotocol)).ProcessSocketAsync();
                        }
                        catch (Exception exc)
                        {
                            // 原始异常错误信息
                            var error = exc is TargetInvocationException && exc.InnerException != null ? exc.InnerException : exc;

                            // 处理请求中发生错误
                            Logger.Fatal(exc.Message, error);

                            // 发送错误消息
                            await progress.SendAsync(Message.Create(error));

#if DEBUG
                            // 关闭连接，在调试模式下响应详细的错误信息
                            await socket.CloseAsync(WebSocketCloseStatus.InternalServerError, error.Message, cancellationToken);
#else
                            // 关闭连接，在非调试模式下响应 Internal Server Error
                            await socket.CloseAsync(WebSocketCloseStatus.InternalServerError, "Internal Server Error", cancellationToken);
#endif

                            // 记录跟踪信息
                            Logger.InfoFormat("WebSocket Close Error: {0}", request.RequestUri);
                            return;
                        }

                        // 正常关闭连接
                        await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Close Normal", cancellationToken);
                    }
                }
            });

            // 升级和切换协议，并发送允许的子协议列表
            return Task.FromResult(request.CreateResponse(HttpStatusCode.SwitchingProtocols).SetAcceptedSubProtocol(subprotocol));
        }

        /// <summary>
        /// 向客户端响应一个消息，不用于 WebSocket 请求。确保总是会向客户端返回一个 Message 对象。
        /// </summary>
        /// <param name="request">目标请求消息。</param>
        /// <param name="message">已经创建的消息，如果为 null，则响应一个 EmptyMessage。</param>
        /// <returns>响应给客户端的响应消息。</returns>
        HttpResponseMessage CreateResponse(HttpRequestMessage request, Message message)
        {
            HttpResponseMessage response = null;

            if (message != null && message is StatusMessage)
            {
                // 发送 HTTP 状态代码
                var sm = message as StatusMessage;
                response = request.CreateResponse(sm.Status);
                response.ReasonPhrase = sm.Description;
            }
            else
            {
                // 发送响应内容
                response = request.CreateResponse(HttpStatusCode.OK);

                if (message != null)
                {
                    message.ToResponseContent(response);
                }
            }

            // 如果消息中包含了 COOKIE，则添加 COOKIE
            if (message != null && message.Cookies.Count > 0)
            {
                response.Headers.AddCookies(message.Cookies);
            }

            return response;
        }
    }
}
